using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.Net;
using System.Web;
using System.Web.Services;
using System.Web.Services.Discovery;
using System.Web.Services.Description;
using System.IO;
using System.Text;
using System.Xml;
using System.Collections;

namespace MH.WsdlWorks.ProjectModel
{
    [XmlType()]
    public class Wsdl : Node
    {
        // the url of the Wsdl document
        string url;
        [XmlAttributeAttribute]
        public string Url
        {
            get { return url; }
            set { url = value; }
        }

        string serviceUrl;
        [XmlAttributeAttribute]
        public string ServiceUrl
        {
            get { return serviceUrl; }
            set { serviceUrl = value; }
        }

        MessageFile wsdlFile;
        [XmlElementAttribute]
        public MessageFile WsdlFile
        {
            get { return wsdlFile; }
            set { wsdlFile = value; }
        }

        List<MessageFile> wsdlFiles = new List<MessageFile>();
        [XmlArray]
        public List<MessageFile> WsdlFiles
        {
            get { return wsdlFiles; }
            set { wsdlFiles = value; }
        }

        MessageFile wsdlMapFile;
        [XmlElementAttribute]
        public MessageFile WsdlMapFile
        {
            get { return wsdlMapFile; }
            set { wsdlMapFile = value; }
        }

        List<Operation> operations = new List<Operation>();
        [XmlArray]
        public List<Operation> Operations
        {
            get { return operations; }
            set { operations = value; }
        }

        // this constructor is called by the XML serializer when the project is recreated by
        // loading it from disk.
        public Wsdl() { }

        // this constructor is called when a Wsdl object is created by the user, so here
        // we should interegate the WSDL file to create operations etc
        public Wsdl(INode parent, string name, string url) : base(parent, name)
        {
            this.url = url;
            Create();
        }

        /// <summary>
        /// Loads the WSDL file from the given url and creates the project structure from it.
        /// </summary>
        private void Create()
        {
            // download the wsdl documents
            DiscoveryClientProtocol client = new DiscoveryClientProtocol();
            client.Credentials = CredentialCache.DefaultCredentials;
            client.DiscoverAny(this.url);
            client.ResolveAll();

            this.Name = Utilities.GetUniqueFolderName(GetServiceName(client), this.Parent.GetPath());
            this.ServiceUrl = GetWebServiceUrl(client);

            // write wsdl documents to disk
            CreateFolder();
            string wsdlMapFileName = this.Name + ".wsdlMap.xml";
            DiscoveryClientResultCollection documents = client.WriteAll(this.GetPath(), wsdlMapFileName);
            this.wsdlMapFile = new MessageFile(this, wsdlMapFileName);
            foreach (DiscoveryClientResult document in documents)
            {
                MessageFile messageFile = new MessageFile(this, Path.GetFileName(document.Filename));
                wsdlFiles.Add(messageFile);
            }

            // create the opeartions
            CreateOperations(client);
        }


        private static string GetServiceName(DiscoveryClientProtocol client)
        {
            return MakeValidFilename(XmlConvert.DecodeName(GetService(client).Name));
        }

        private static Service GetService(DiscoveryClientProtocol client)
        {
            foreach (DictionaryEntry entry in client.Documents)
            {
                ServiceDescription serviceDescription = entry.Value as ServiceDescription;
                if ((serviceDescription != null) && (serviceDescription.Services.Count != 0))
                {
                    return serviceDescription.Services[0];
                }
            }
            throw new ApplicationException("No Service found in WSDL");
        }

        private static string MakeValidFilename(string filename)
        {
            StringBuilder builder = new StringBuilder(filename.Length);
            for (int i = 0; i < filename.Length; i++)
            {
                char c = filename[i];
                if (char.IsLetterOrDigit(c) || (c == '_'))
                {
                    builder.Append(c);
                }
            }
            return builder.ToString();
        }

        private void CreateFolder()
        {
            if (!Directory.Exists(this.GetPath()))
            {
                Directory.CreateDirectory(this.GetPath());
            }
        }

        private void CreateOperations(DiscoveryClientProtocol client)
        {
            foreach (DictionaryEntry entry in client.Documents)
            {
                ServiceDescription serviceDescription = entry.Value as ServiceDescription;
                if ((serviceDescription != null))
                {
                    Binding binding = GetSoapBinding(serviceDescription);
                    if (binding != null)
                    {
                        foreach (OperationBinding wsdlOperation in binding.Operations)
                        {
                            MH.WsdlWorks.ProjectModel.Operation operation = new Operation(this, wsdlOperation);
                            this.operations.Add(operation);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Returns the SOAP 1.1 binding from the given serviceDescription
        /// </summary>
        /// <param name="serviceDescription"></param>
        /// <returns></returns>
        private Binding GetSoapBinding(ServiceDescription serviceDescription)
        {
            foreach (Binding binding in serviceDescription.Bindings)
            {
                foreach (object extension in binding.Extensions)
                {
                    // extension must be SoapBinding, we don't want anything that's derived from SoapBinding
                    // like Soap12Binding, that why I'm not using 'is'
                    if (extension.GetType() == typeof(System.Web.Services.Description.SoapBinding))
                    {
                        return binding;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Try and get the service location. If we can't find it, just return an empty string
        /// </summary>
        /// <param name="serviceDescription"></param>
        /// <returns></returns>
        private static string GetWebServiceUrl(DiscoveryClientProtocol client)
        {
            Service service = GetService(client);
            if (service.Ports.Count == 0) return string.Empty;

            foreach (Port port in service.Ports)
            {
                foreach (object extension in port.Extensions)
                {
                    SoapAddressBinding addressBinding = extension as SoapAddressBinding;
                    if (addressBinding != null)
                    {
                        return addressBinding.Location;
                    }
                }
            }
            return string.Empty;
        }

        public override void SetParent(INode parent)
        {
            this.Parent = parent;
            this.WsdlFile.SetParent(this);
            foreach (Operation operation in this.operations)
            {
                operation.SetParent(this);
            }
        }
    }
}
